using System;
using Lidgren.Library.Network;
using Microsoft.Xna.Framework;
using System.Text;

namespace SharpWind.Network
{
	public class NetworkManager
	{
		private NetClient client = null;

		private NetAppConfiguration config = null;

		private NetLog log = null;

		private long processingTime = 100;

		private Connection serverConnection = null;

		private VoiceManager voice = null;

		private string reason = null;

		private string playerName = string.Empty;

		public event ConnectionEventHandler Connected = null;

		public event ConnectionEventHandler Disconnected = null;

		public event ServerDiscoveredEventHandler ServerDiscovered = null;

		public event VoiceEventHandler VoiceReceived = null;

		public NetworkManager()
		{
			this.config = new NetAppConfiguration( "SharpWind", 5071 );

			this.log = new NetLog();
			this.log.IgnoreTypes = NetLogEntryType.Verbose;
			this.log.LogEvent	+= new EventHandler<NetLogEventArgs>( OnLogEvent );

			this.client = new NetClient( this.config, this.log );
			this.client.ServerDiscovered	+= new EventHandler<NetServerDiscoveredEventArgs>( OnServerDiscovered );
			this.client.StatusChanged		+= new EventHandler<NetStatusEventArgs>( OnStatusChanged );
		}

		public void DiscoverLocalServers()
		{
			this.client.DiscoverLocalServers( 5070 );
		}

		public bool Connect( string host, int port, string playerName )
		{
			return Connect( host, port, playerName, string.Empty );
		}

		public bool Connect( string host, int port, string playerName, string password )
		{
			if( string.IsNullOrEmpty( playerName ) || string.IsNullOrEmpty( host ) )
			{
				return false;
			}

			this.playerName = playerName;

			// Custom Login Data
			bool passwordIncluded	= false;
			byte[] playerNameBytes	= Encoding.Unicode.GetBytes( playerName );
			byte[] passwordBytes	= null;

			if( !string.IsNullOrEmpty( password ) )
			{
				passwordIncluded	= true;
				passwordBytes		= Encoding.Unicode.GetBytes( password );
			}

			byte[] customData		= null;
			if( passwordIncluded )
			{
				customData = new byte[sizeof( bool ) + sizeof( int ) + passwordBytes.Length + sizeof( int ) + playerNameBytes.Length];
				
				int index = 0;
				BitConverter.GetBytes( passwordIncluded ).CopyTo( customData, index );
				
				index += sizeof( bool );
				BitConverter.GetBytes( passwordBytes.Length ).CopyTo( customData, index );

				index += sizeof( int );
				passwordBytes.CopyTo( customData, index );

				index += passwordBytes.Length;
				BitConverter.GetBytes( playerNameBytes.Length ).CopyTo( customData, index );

				index += sizeof( int );
				playerNameBytes.CopyTo( customData, index );
			}
			else
			{
				customData = new byte[sizeof( bool ) + sizeof( int ) + playerNameBytes.Length];

				int index = 0;
				BitConverter.GetBytes( passwordIncluded ).CopyTo( customData, index );

				index += sizeof( bool );
				BitConverter.GetBytes( playerNameBytes.Length).CopyTo( customData, index );

				index += sizeof( int );
				playerNameBytes.CopyTo( customData, index );
			}

			return this.client.Connect( host, port, customData );
		}

		internal void SendVoice( byte[] buffer )
		{
			NetMessage msg = new NetMessage();
			msg.Write( "sw" );
			msg.Write( (byte)PacketType.Engine );
			msg.Write( (byte)PacketCommand.Voice );
			msg.Write( this.playerName );
			msg.Write( buffer.Length );
			msg.Write( buffer );

			this.client.SendMessage( msg, NetChannel.Sequenced1 );
		}

		public void Disconnect()
		{
			Disconnect( Reason.None );
		}

		public void Disconnect( string reason )
		{
			this.client.Disconnect( reason );
		}

		private void OnLogEvent( object sender, NetLogEventArgs e )
		{
		}

		public void Shutdown()
		{
			this.client.Shutdown( "Application exiting" );
		}

		internal void Update( GameTime gameTime )
		{
			long endTime = gameTime.TotalRealTime.Milliseconds + this.processingTime;

			this.client.Heartbeat();
		
			NetMessage msg;
			while( endTime > gameTime.TotalRealTime.Milliseconds && ( msg = this.client.ReadMessage() ) != null )
			{
				HandleMessage( msg );
			}
		}

		private void HandleMessage( NetMessage msg )
		{
			string header = msg.ReadString();
			if( header.Equals( "sw" ) )
			{
				byte packetType = msg.ReadByte();
				if( (PacketType)packetType == PacketType.Engine )
				{
					byte packetCommand = msg.ReadByte();
					switch( (PacketCommand)packetCommand )
					{
						case PacketCommand.Voice:
						{
							string playerName = msg.ReadString();
							int bufferLength  = msg.ReadInt32();
							byte[] buffer	  = msg.ReadBytes( bufferLength );

							if( VoiceReceived != null )
							{
								VoiceReceived( this, new VoiceEventArgs( buffer ) );
							}

							break;
						}
					}
				}
				else
				{
				}
			}
		}

		private void OnServerDiscovered( object sender, NetServerDiscoveredEventArgs e )
		{
			if( ServerDiscovered != null )
			{
				ServerDiscovered( this, new ServerDiscoveredEventArgs( e.ServerInformation.MaxConnections,
					e.ServerInformation.NumConnected, e.ServerInformation.RemoteEndpoint,
					e.ServerInformation.ServerName ) );
			}
		}

		private void OnStatusChanged( object sender, NetStatusEventArgs e )
		{
			switch( e.PreviousStatus )
			{
				case NetConnectionStatus.Disconnected:
				{
					if( e.Connection.Status == NetConnectionStatus.Connecting )
					{
						this.reason = string.Empty;
					}

					break;
				}

				case NetConnectionStatus.Connecting:
				{
					if( e.Connection.Status == NetConnectionStatus.Connected )
					{
						this.serverConnection = new Connection( e.Connection );
						//this.voice = new VoiceManager();

						OnConnected( new ConnectionEventArgs( e.Reason, this.serverConnection ) );
					}

					if( e.Connection.Status == NetConnectionStatus.Disconnected )
					{
						this.reason = e.Reason;
						this.serverConnection = null;
						if( this.voice != null )
						{
							this.voice.Dispose();
							this.voice = null;
						}

						OnDisconnected( new ConnectionEventArgs( e.Reason, this.serverConnection ) );
					}

					break;
				}

				case NetConnectionStatus.Connected:
				{
					if( e.Connection.Status == NetConnectionStatus.Disconnected )
					{
						this.reason = e.Reason;
						this.serverConnection = null;
						if( this.voice != null )
						{
							this.voice.Dispose();
							this.voice = null;
						}

						OnDisconnected( new ConnectionEventArgs( e.Reason, this.serverConnection ) );
					}

					break;
				}
			}
		}

		private void OnConnected( ConnectionEventArgs e )
		{
			if( Connected != null )
			{
				Connected( this, e );
			}
		}

		private void OnDisconnected( ConnectionEventArgs e )
		{
			if( Disconnected != null )
			{
				Disconnected( this, e );
			}
		}

		public Connection ServerConnection
		{
			get
			{
				return this.serverConnection;
			}
		}

		public NetConnectionStatus Status
		{
			get
			{
				return this.client.Status;
			}
		}

		public string CurrentReason
		{
			get
			{
				return this.reason;
			}
		}
	}
}